<?php
namespace Websocket;
require_once 'Net/Connection.php';
require_once 'WebsocketConnection.php';

use Net\Connection;
use Net\Event;
use Net\EventBaseTrait;
use Websocket\WebsocketConnection;

class WebsocketServer extends WebsocketConnection
{
    protected $_connectionMap = [];
    
    public function __construct($socket=null)
    {
        parent::__construct($socket);
        
        $this->onNewConnect(function(Event $event) {
            /** @var WebsocketConnection $connection */
            $connection = $event->getConnection();
        
            $connection->onRecv(function(Event $event) use($connection) {
                $message = $event->getMessage();
                $event->stopTransmit();
                
                if(!$connection->handshake) {
                    $this->handshake($connection, $message);
                } else {
                    $buffer = $connection->getBuffer();
                    $buffer->load($message);
                    
                    $messages = $buffer->fetchAllMessages();
                    
                    foreach ($messages as $message) {
                        $connection->triggerOnMessage([Event::PARAM_MESSAGE => $message]);
                    }
                    
                    if($buffer->isClosed()) {
                        $connection->close();
                    }
                }
            });
        });
    }
    
    public function handshake(WebsocketConnection $connection, $message) {
        $e = $this->triggerBeforeHandshake([Event::PARAM_CONNECTION => $connection, Event::PARAM_MESSAGE => $message]);
    
        if($e->isPreventDefault()) {
            return ;
        }
    
        list($header, $body) = self::parseHttpProtocol($message);
    
        if(strtolower($header['connection']) == 'upgrade' && strtolower($header['upgrade']) == 'websocket') {
            $connection->handshake = true;
            $connection->handshakeheader['request_header'] = $header;
            $response = $this->_getWebsocketHandshakeResponse($header['sec-websocket-key']);
            $connection->send($response);
            $this->addConnection($connection);
    
            $connection->onClose(function() use($connection) {
                $this->removeConnection($connection);
            });
            
            $this->triggerOnHandshake([Event::PARAM_CONNECTION => $connection, 'header' => $header]);
        } else {
            $peername = $connection->getpeername();
            $this->triggerOnFailHandshake([
                Event::PARAM_CONNECTION => $connection,
                'peername' => $peername,
                Event::PARAM_MESSAGE => $message,
                'reason' => '协议错误'
            ]);
            $connection->close();
        }
    }
    
    public function triggerBeforeHandshake( $params) {
        return $this->triggerBefore('handshake', $params);
    }
    
    public function triggerOnHandshake($params) {
        return $this->triggerOn('handshake', $params);
    }
    
    public function triggerOnFailHandshake($params) {
        return $this->triggerOn('fail_handshake', $params);
    }
    
    /**
     * @param $callback
     * @return WebsocketServer
     */
    public function onHandshake($callback) {
        return $this->on('handshake', $callback);
    }
    
    public function onFailHandshake($callback)
    {
        return $this->on('fail_handshake', $callback);
    }
    
    protected function _getWebsocketHandshakeResponse($key) {
        $newKey = $this->encodeKey($key);
        $response = "HTTP/1.1 101 Switching Protocols\r\n";
        $response .= "Upgrade: websocket\r\n";
        $response .= "Connection: Upgrade\r\n";
        $response .= "Sec-WebSocket-Accept: $newKey\r\n\r\n";
        
        return $response;
    }
    
    public function addConnection(WebsocketConnection $connection) {
        $id = $connection->getId();
        $this->_connectionMap [$id] = $connection;
        return $this;
    }
    
    public function removeConnection(WebsocketConnection $connection) {
        $id = $connection->getId();
        
        if(isset($this->_connectionMap [$id])) {
            unset($this->_connectionMap [$id]);
        }
        
        return $this;
    }
    
    /**
     * @param $id
     * @return WebsocketConnection
     */
    public function getConnection($id) {
        return $this->_connectionMap[$id];
    }
}
